package top.hekun.study.otp;

import cn.hutool.core.codec.Base32;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HMac;

public class OtpUtils {

    /**
     * 生成验证 Key
     *
     * @return 验证 Key
     */
    public static String generateTFAKey() {
        return RandomUtil.randomString(RandomUtil.BASE_CHAR, 32).toUpperCase();
    }

    /**
     * 验证code
     *
     * @param key  验证 Key
     * @param code 验证码
     * @return
     */
    public static boolean validateTFACode(String key, String code) {
        int validCode = Integer.valueOf(code);
        return validateCurrentNumber(key, validCode, 60_000);
    }


    public static boolean validateCurrentNumber(String base32Secret, int authNumber,
                                                int windowMillis) {
        return validateCurrentNumber(base32Secret, authNumber, windowMillis,
                System.currentTimeMillis(),
                30);
    }

    /**
     * @param key             秘钥
     * @param authNumber      验证码
     * @param windowMillis
     * @param timeMillis
     * @param timeStepSeconds
     * @return
     */
    public static boolean validateCurrentNumber(String key, int authNumber,
                                                int windowMillis, long timeMillis,
                                                int timeStepSeconds) {
        long fromTimeMillis = timeMillis;
        long toTimeMillis = timeMillis;
        if (windowMillis > 0) {
            fromTimeMillis -= windowMillis;
            toTimeMillis += windowMillis;
        }
        long timeStepMillis = timeStepSeconds * 1000L;
        for (long millis = fromTimeMillis; millis <= toTimeMillis; millis += timeStepMillis) {
            int generatedNumber = generateNumber(key, millis, timeStepSeconds);
            if (generatedNumber == authNumber) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据key 获取code
     *
     * @param key
     * @return
     */
    public static int generateNumber(String key) {
        return generateNumber(key, System.currentTimeMillis(), 30);
    }

    /**
     * 根据key 获取code
     *
     * @param base32Secret    秘钥
     * @param timeMillis      当前时间戳
     * @param timeStepSeconds 周期
     * @return
     */
    public static int generateNumber(String base32Secret, long timeMillis, int timeStepSeconds) {

        byte[] key = Base32.decode(base32Secret);

        byte[] data = new byte[8];
        long value = timeMillis / 1000 / timeStepSeconds;
        for (int i = 7; value > 0; i--) {
            data[i] = (byte) (value & 0xFF);
            value >>= 8;
        }
        HMac hMac = SecureUtil.hmacSha1(key);
        // 加密  当前时间 的处理结果
        byte[] hash = hMac.digest(data);

        // take the 4 least significant bits from the encrypted string as an offset
        //  将加密字符串中的4个最低有效位作为偏移
        int offset = hash[hash.length - 1] & 0xF;

        // We're using a long because Java hasn't got unsigned int.
        // 我们使用long是因为Java没有无符号int。
        long truncatedHash = 0;
        for (int i = offset; i < offset + 4; ++i) {
            truncatedHash <<= 8;
            // get the 4 bytes at the offset
            // 获取偏移处的4个字节
            truncatedHash |= hash[i] & 0xFF;
        }
        // cut off the top bit
        // 切掉顶部的bit
        truncatedHash &= 0x7FFFFFFF;

        // the token is then the last 6 digits in the number
        // 则令牌是数字的最后6位
        truncatedHash %= 1_000_000;
        // this is only 6 digits so we can safely case it
        // 这只有6位数，所以我们可以放心地用 int
        return (int) truncatedHash;
    }
}
